package org.dtrevien.metamodel.java.visitor.impl;

import java.util.List;

import org.dtrevien.metamodel.graphviz.AList;
import org.dtrevien.metamodel.graphviz.AttrList;
import org.dtrevien.metamodel.graphviz.Graph;
import org.dtrevien.metamodel.graphviz.GraphType;
import org.dtrevien.metamodel.graphviz.GraphvizModel;
import org.dtrevien.metamodel.graphviz.NodeId;
import org.dtrevien.metamodel.graphviz.Op;
import org.dtrevien.metamodel.graphviz.edgerhs.EdgeRHSNode;
import org.dtrevien.metamodel.graphviz.stmt.impl.EdgeStmtNode;
import org.dtrevien.metamodel.graphviz.stmt.impl.NodeStmt;
import org.dtrevien.metamodel.graphviz.stmt.types.LinkType;
import org.dtrevien.metamodel.java.Attribut;
import org.dtrevien.metamodel.java.Classe;
import org.dtrevien.metamodel.java.ClasseAggregationReference;
import org.dtrevien.metamodel.java.ClasseCompositionReference;
import org.dtrevien.metamodel.java.ClasseExtendReference;
import org.dtrevien.metamodel.java.ClasseImplementReference;
import org.dtrevien.metamodel.java.Constructeur;
import org.dtrevien.metamodel.java.Import;
import org.dtrevien.metamodel.java.Methode;
import org.dtrevien.metamodel.java.ObjectList;
import org.dtrevien.metamodel.java.Relationship;
import org.dtrevien.metamodel.java.Systeme;
import org.dtrevien.metamodel.java.Value;
import org.dtrevien.metamodel.java.Visibility;
import org.dtrevien.metamodel.java.visitor.Visitor;

/**
 * Generates a GraphvizModel from a JavaModel
 * 
 * @author davidtrevien
 *
 */
public class GraphvizConverter extends Visitor {

	/**
	 * The result of this converter
	 * Instantiate at begin, filled along visit, and available on finish conversion
	 */
	protected GraphvizModel model;
	
	protected Graph g;
	protected NodeStmt node;
	protected List<AttrList> attrList;
	protected NodeId nodeId;
	protected AList currentAList;
	
	protected Classe c;

	public GraphvizConverter() {
		this.model = new GraphvizModel();
		this.g = new Graph();
		this.model.getGraphs().add(this.g);
		this.g.setType(GraphType.digraph);
		this.g.setName("gen");
	}
	
	@Override
	public void visiteClasse(Classe c) {
		System.out.println(c.getName());
		
		this.c = c;
		this.nodeId = new NodeId(c.getName(), null);
		
		//Set the subgraph
		this.node = new NodeStmt();
		this.node.setName(c.getName());
		this.g.getStmts().add(this.node);
		
		//Visit all extends
		for (ClasseExtendReference ext : c.getExtends()) {
			ext.accept(this);
		}
		
		//Visit all implements
		for (ClasseImplementReference impl : c.getImplementations()) {
			impl.accept(this);
		}
		
		//Visit all attributs
		for (Attribut a : c.getAttributs()) {
			a.accept(this);
		}
		
		//Visit all methods
		for (Methode m : c.getMethods()) {
			m.accept(this);
		}
	}

	@Override
	public void visiteValue(Value c) {
		//Filling classAttributes
		if (c.getRelationship().equals(Relationship.CLASS_ATTRIBUTE)) {
			System.out.println("\t\tAttribute : "+Visibility.visibility2string(c.getVisibility())+c.getType().getName()+" "+c.getName());
			AList e = new AList(Visibility.visibility2string(c.getVisibility())+c.getName(), c.getType().getName());
			this.node.getAttrLists().get(0).getaLists().add(e);
		}
		//Filling methods
		else if (c.getRelationship().equals(Relationship.METHOD_PARAM)) {
			System.out.println("\t\tMethod param : "+c.getType().getName()+" "+c.getName());
			this.currentAList.appendToName(c.getName(), " : ", c.getType().getName());
		}
		//Filling values (create an edge)
		else {
			System.out.println("\tValue : "+this.c.getName()+" ---> 1 "+c.getType().getName()+" "+c.getName());
			EdgeStmtNode edge = new EdgeStmtNode();
			edge.setNodeId(this.nodeId);
			edge.setLinkType(LinkType.DIRECT);
			edge.setHeadLabel(c.getName()+" "+c.getCard0());
			edge.setTailLabel(c.getCard1());
			
			NodeId n = new NodeId(c.getType().getName(), null);
			EdgeRHSNode rhs = new EdgeRHSNode(Op.directed, n);
			edge.getEdgeRHSs().add(rhs);
			this.g.getStmts().add(edge);
		}
	}
	
	@Override
	public void visiteClasseAggregationReference(ClasseAggregationReference c) {
		System.out.println("\tAggregation : "+this.c.getName()+" <>-- "+c.getType().getName()+" "+c.getName());
		EdgeStmtNode edge = new EdgeStmtNode();
		edge.setNodeId(this.nodeId);
		edge.setLinkType(LinkType.AGGREGATION);
		edge.setHeadLabel(c.getName()+" "+c.getCard1());
		edge.setTailLabel(c.getCard0());
		
		NodeId n = new NodeId(c.getType().getName(), null);
		EdgeRHSNode rhs = new EdgeRHSNode(Op.directed, n);
		edge.getEdgeRHSs().add(rhs);
		this.g.getStmts().add(edge);
	}
	

	@Override
	public void visiteClasseCompositionReference(ClasseCompositionReference c) {
		System.out.println("\tComposition : "+this.c.getName()+" ++-- "+c.getType().getName()+" "+c.getName());
		
		EdgeStmtNode edge = new EdgeStmtNode();
		edge.setNodeId(this.nodeId);
		edge.setLinkType(LinkType.COMPOSITION);
		edge.setHeadLabel(c.getName()+" "+c.getCard0());
		edge.setTailLabel(c.getCard1());
		
		NodeId n = new NodeId(c.getType().getName(), null);
		EdgeRHSNode rhs = new EdgeRHSNode(Op.directed, n);
		edge.getEdgeRHSs().add(rhs);
		this.g.getStmts().add(edge);
	}

	@Override
	public void visiteClasseExtendsReference(ClasseExtendReference c) {
		System.out.println("\tExtends : "+this.c.getName()+" --!> "+c.getType().getName()+" "+c.getName());
		
		EdgeStmtNode edge = new EdgeStmtNode();
		edge.setNodeId(this.nodeId);
		edge.setLinkType(LinkType.INHERITANCE);
		
		NodeId n = new NodeId(c.getType().getName(), null);
		EdgeRHSNode rhs = new EdgeRHSNode(Op.directed, n);
		edge.getEdgeRHSs().add(rhs);
		this.g.getStmts().add(edge);
	}


	@Override
	public void visiteClasseImplementReference(ClasseImplementReference c) {
		System.out.println("\tImplements : "+this.c.getName()+" -.-!> "+c.getType().getName()+" "+c.getName());
		
		EdgeStmtNode edge = new EdgeStmtNode();
		edge.setNodeId(this.nodeId);
		edge.setLinkType(LinkType.IMPLEMENTATION);
		
		NodeId n = new NodeId(c.getType().getName(), null);
		EdgeRHSNode rhs = new EdgeRHSNode(Op.directed, n);
		edge.getEdgeRHSs().add(rhs);
		this.g.getStmts().add(edge);
	}

	@Override
	public void visiteObjectList(ObjectList c) {
		System.out.println("\tObject list : "+this.c.getName()+" ---> * "+c.getType().getName()+" "+c.getName());
		
		EdgeStmtNode edge = new EdgeStmtNode();
		edge.setNodeId(this.nodeId);
		edge.setLinkType(Relationship.relationShip2LinkType(c.getRelationship()));
		edge.setHeadLabel(c.getName()+" "+c.getCard0());
		edge.setTailLabel(c.getCard1());
		
		NodeId n = new NodeId(c.getType().getName(), null);
		EdgeRHSNode rhs = new EdgeRHSNode(Op.directed, n);
		edge.getEdgeRHSs().add(rhs);
		this.g.getStmts().add(edge);
	}
	

	@Override
	public void visiteMethode(Methode m) {
		System.out.println("\t\tMethode : "+Visibility.visibility2string(m.getVisibility())+m.getType().getName()+" "+m.getName());
		this.currentAList = new AList(Visibility.visibility2string(m.getVisibility())+m.getName(), m.getType().getName());
		
		//Assign params
		this.currentAList.appendToName("(");
		this.node.getAttrLists().get(1).getaLists().add(this.currentAList);
		int i = 0;
		for (Attribut a : m.getParams()) {
			a.accept(this);
			if (i < m.getParams().size()-1) this.currentAList.appendToName(", ");
			i++;
		}
		this.currentAList.appendToName(")");
	}
	
	@Override
	public void visiteSystem(Systeme s) {
		for (Classe c : s.getClasses()) {
			c.accept(this);
		}
	}

	@Override
	public void visitImport(Import i) {
		
	}
	
	@Override
	public void visitConstructeur(Constructeur c) {
		
	}
	
	/**
	 * Get the generated model
	 * 
	 * @return
	 */
	public GraphvizModel getGraphvizModel() {
		return this.model;
	}

}
